package top.codef.secure.login;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import org.springframework.security.core.GrantedAuthority;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.MissingNode;

public class CerberusAuthenticationTokenDeserializer extends JsonDeserializer<CerberusAuthenticationToken> {

	/**
	 * This method construct {@link UsernamePasswordAuthenticationToken} object from
	 * serialized json.
	 * 
	 * @param jp   the JsonParser
	 * @param ctxt the DeserializationContext
	 * @return the user
	 * @throws IOException             if a exception during IO occurs
	 * @throws JsonProcessingException if an error during JSON processing occurs
	 */
	@Override
	public CerberusAuthenticationToken deserialize(JsonParser jp, DeserializationContext ctxt)
			throws IOException, JsonProcessingException {
		CerberusAuthenticationToken token = null;
		ObjectMapper mapper = (ObjectMapper) jp.getCodec();
		JsonNode jsonNode = mapper.readTree(jp);
		Boolean authenticated = readJsonNode(jsonNode, "authenticated").asBoolean();
		JsonNode principalNode = readJsonNode(jsonNode, "principal");
		Object principal = null;
		if (principalNode.isObject()) {
			principal = mapper.readValue(principalNode.traverse(mapper), Object.class);
		} else {
			principal = principalNode.asText();
		}
		JsonNode credentialsNode = readJsonNode(jsonNode, "credentials");
		Object credentials;
		if (credentialsNode.isNull() || credentialsNode.isMissingNode()) {
			credentials = null;
		} else {
			credentials = credentialsNode.asText();
		}
		List<GrantedAuthority> authorities = mapper.readValue(readJsonNode(jsonNode, "authorities").traverse(mapper),
				new TypeReference<List<GrantedAuthority>>() {
				});
		Map<String, Object> map = mapper.readValue(readJsonNode(jsonNode, "additionalParams").traverse(mapper),
				new TypeReference<Map<String, Object>>() {
				});
		String optional = readJsonNode(jsonNode, "optional").asText();
		if (authenticated) {
			token = new CerberusAuthenticationToken(principal, credentials, optional, authorities, map);
		} else {
			token = new CerberusAuthenticationToken(principal, credentials, optional, map);
		}
		JsonNode detailsNode = readJsonNode(jsonNode, "details");
		if (detailsNode.isNull() || detailsNode.isMissingNode()) {
			token.setDetails(null);
		} else {
			token.setDetails(detailsNode);
		}
		return token;
	}

	private JsonNode readJsonNode(JsonNode jsonNode, String field) {
		return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
	}
}
